package org.hepeng.fabric.contract.proxy;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.hepeng.fabric.contract.ContractProvider;
import org.hepeng.fabric.contract.annotation.ContractAPI;
import org.hyperledger.fabric.client.Proposal;
import org.hepeng.fabric.contract.annotation.TxFunction;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
 * @author he peng
 * @date 2022/3/23
 */

public class ContractProxy implements InvocationHandler {

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private final Class<?> contractInterface;

    private final ContractProvider contractProvider;

    public ContractProxy(Class<?> contractInterface, ContractProvider contractProvider) {
        this.contractInterface = contractInterface;
        this.contractProvider = contractProvider;
    }

    public static <T> T newProxy(Class<T> contractInterface , ContractProvider contractProvider) {

        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader() ,
                new Class[]{contractInterface},
                new ContractProxy(contractInterface , contractProvider));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        TxFunction txFunction = method.getDeclaredAnnotation(TxFunction.class);
        if (Objects.isNull(txFunction)) {
            return method.invoke(proxy , args);
        }

        Object result = null;

        ContractAPI contractAPIAtn = contractInterface.getDeclaredAnnotation(ContractAPI.class);
        org.hyperledger.fabric.client.Contract contract = contractProvider.getContract(contractAPIAtn.name());

        Proposal.Builder builder = contract.newProposal(method.getName());
        if (Objects.equals(TxFunction.ArgType.PRIMITIVE , txFunction.argType())) {

            builder.addArguments(TxFunction.ArgType.PRIMITIVE.convert(args));
        } else if (Objects.equals(TxFunction.ArgType.JSON , txFunction.argType())) {

            builder.addArguments(TxFunction.ArgType.JSON.convert(args));
        }

        Proposal proposal = builder.build();

        if (Objects.equals(TxFunction.Type.QUERY , txFunction.type())) {
            byte[] bytes = proposal.evaluate();
            result = OBJECT_MAPPER.readValue(new String(bytes, StandardCharsets.UTF_8) , method.getReturnType());
        } else if (Objects.equals(TxFunction.Type.SUBMIT , txFunction.type())) {
            proposal.endorse().submitAsync();
        }

        return result;
    }
}